Explorez les techniques TypeScript avancées utilisant les gabarits de chaînes pour une manipulation puissante des types. Apprenez à analyser et valider les types.
Analyse des Gabarits de Chaînes de Caractères TypeScript : Manipulation Avancée des Types de Chaînes
Le système de types de TypeScript fournit des outils puissants pour manipuler et valider les données à la compilation. Parmi ces outils, les gabarits de chaînes de caractères (template literals) offrent une approche unique pour la manipulation des types de chaînes. Cet article explore les aspects avancés de l'analyse des gabarits de chaînes, montrant comment créer une logique sophistiquée au niveau des types pour les données basées sur des chaînes.
Que sont les Types de Gabarits de Chaînes de Caractères ?
Les types de gabarits de chaînes de caractères, introduits dans TypeScript 4.1, vous permettent de définir des types de chaînes basés sur des littéraux de chaîne et d'autres types. Ils utilisent les apostrophes inverses (`) pour définir le type, de manière similaire aux gabarits de chaînes en JavaScript.
Par exemple :
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorCombination = `${Shade} ${Color}`;
// ColorCombination est maintenant "light red" | "light green" | "light blue" | "dark red" | "dark green" | "dark blue"
Cette fonctionnalité apparemment simple ouvre un large éventail de possibilités pour le traitement des chaînes à la compilation.
Utilisation de Base des Types de Gabarits de Chaînes
Avant de plonger dans les techniques avancées, passons en revue quelques cas d'utilisation fondamentaux.
Concaténation de Littéraux de Chaîne
Vous pouvez facilement combiner des littéraux de chaîne et d'autres types pour créer de nouveaux types de chaînes :
type Greeting = `Hello, ${string}!`;
// Exemple d'utilisation
const greet = (name: string): Greeting => `Hello, ${name}!`;
const message: Greeting = greet("World"); // Valide
const invalidMessage: Greeting = "Goodbye, World!"; // Erreur : Type '"Goodbye, World!"' is not assignable to type '`Hello, ${string}!`'.
Utilisation des Types Union
Les types union vous permettent de définir un type comme une combinaison de plusieurs valeurs possibles. Les gabarits de chaînes peuvent incorporer des types union pour générer des unions de types de chaînes plus complexes :
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/users` | `/api/products`;
type Route = `${HTTPMethod} ${Endpoint}`;
// Route est maintenant "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"
Techniques Avancées d'Analyse des Gabarits de Chaînes
La véritable puissance des types de gabarits de chaînes réside dans leur capacité à être combinés avec d'autres fonctionnalités avancées de TypeScript, telles que les types conditionnels et l'inférence de type, pour analyser et manipuler les types de chaînes.
Inférence de Parties d'un Type de Chaîne
Vous pouvez utiliser le mot-clé infer au sein d'un type conditionnel pour extraire des parties spécifiques d'un type de chaîne. C'est le fondement de l'analyse des types de chaînes.
Considérons un type qui extrait l'extension d'un fichier à partir de son nom :
type GetFileExtension = T extends `${string}.${infer Extension}` ? Extension : never;
// Exemples
type Extension1 = GetFileExtension<"myFile.txt">; // "txt"
type Extension2 = GetFileExtension<"anotherFile.image.jpg">; // "image.jpg" (récupère la dernière extension)
type Extension3 = GetFileExtension<"noExtension">; // never
Dans cet exemple, le type conditionnel vérifie si le type d'entrée T correspond au motif ${string}.${infer Extension}. Si c'est le cas, il infère la partie après le dernier point dans la variable de type Extension, qui est ensuite retournée. Sinon, il retourne never.
Analyse avec Inférences Multiples
Vous pouvez utiliser plusieurs mots-clésinfer dans le même gabarit de chaîne pour extraire simultanément plusieurs parties d'un type de chaîne.
type ParseConnectionString =
T extends `${infer Protocol}://${infer Host}:${infer Port}` ?
{ protocol: Protocol, host: Host, port: Port } : never;
// Exemple
type Connection = ParseConnectionString<"http://localhost:3000">;
// { protocol: "http", host: "localhost", port: "3000" }
type InvalidConnection = ParseConnectionString<"invalid-connection">; // never
Ce type analyse une chaîne de connexion en ses composants de protocole, d'hôte et de port.
Définitions de Types Récursifs pour une Analyse Complexe
Pour des structures de chaînes plus complexes, vous pouvez utiliser des définitions de types récursives. Cela vous permet d'analyser de manière répétée des parties d'un type de chaîne jusqu'à atteindre le résultat souhaité.
Disons que vous souhaitez diviser une chaîne en un tableau de caractères individuels au niveau du type. C'est considérablement plus avancé.
type StringToArray =
T extends `${infer Char}${infer Rest}`
? StringToArray
: Acc;
// Exemple
type MyArray = StringToArray<"hello">; // ["h", "e", "l", "l", "o"]
Explication :
StringToArray<T extends string, Acc extends string[] = []>: Définit un type générique nomméStringToArrayqui prend un type de chaîneTen entrée et un accumulateur optionnelAccqui est par défaut un tableau de chaînes vide. L'accumulateur stockera les caractères au fur et à mesure de leur traitement.T extends `${infer Char}${infer Rest}`: C'est la vérification de type conditionnel. Elle vérifie si la chaîne d'entréeTpeut être divisée en un premier caractèreCharet le reste de la chaîneRest. Le mot-cléinferest utilisé pour capturer ces parties.StringToArray<Rest, [...Acc, Char]>: Si la division réussit, nous appelons récursivementStringToArrayavec leRestde la chaîne et un nouvel accumulateur. Le nouvel accumulateur est créé en propageant l'Accexistant et en ajoutant le caractère actuelCharà la fin. Cela ajoute efficacement le caractère au tableau d'accumulation.Acc: Si la chaîne est vide (le type conditionnel échoue, signifiant qu'il n'y a plus de caractères), nous retournons le tableau accumuléAcc.
Cet exemple démontre la puissance de la récursivité dans la manipulation des types de chaînes. Chaque appel récursif retire un caractère et l'ajoute au tableau jusqu'à ce que la chaîne soit vide.
Travailler avec des Délimiteurs
Les gabarits de chaînes peuvent être facilement utilisés avec des délimiteurs pour analyser des chaînes. Disons que vous voulez extraire des mots séparés par des virgules.
type SplitString =
T extends `${infer First}${D}${infer Rest}`
? [First, ...SplitString]
: [T];
// Exemple
type Words = SplitString<"apple,banana,cherry", ",">; // ["apple", "banana", "cherry"]
Ce type divise récursivement la chaîne à chaque occurrence du délimiteur D.
Applications Pratiques
Ces techniques avancées d'analyse de gabarits de chaînes ont de nombreuses applications pratiques dans les projets TypeScript.
Validation des Données
Vous pouvez valider des données basées sur des chaînes par rapport à des motifs spécifiques à la compilation. Par exemple, valider des adresses e-mail, des numéros de téléphone ou des numéros de carte de crédit. Cette approche fournit un retour précoce et réduit les erreurs d'exécution.
Voici un exemple de validation d'un format d'adresse e-mail simplifié :
type EmailFormat = `${string}@${string}.${string}`;
const validateEmail = (email: string): email is EmailFormat => {
// En réalité, une regex beaucoup plus complexe serait utilisée pour une validation correcte des e-mails.
// Ceci est uniquement à des fins de démonstration.
return /.+@.+\..+/.test(email);
}
const validEmail: EmailFormat = "user@example.com"; // Valide
const invalidEmail: EmailFormat = "invalid-email"; // Le type 'string' n'est pas assignable au type '`${string}@${string}.${string}`'.
if(validateEmail(validEmail)) {
console.log("E-mail valide");
}
if(validateEmail("invalid-email")) {
console.log("Ceci ne s'affichera pas.");
}
Bien que la validation à l'exécution avec une regex soit toujours nécessaire dans les cas où le vérificateur de type ne peut pas entièrement appliquer la contrainte (par exemple, lors du traitement d'entrées externes), le type EmailFormat fournit une première ligne de défense précieuse à la compilation.
Génération de Points de Terminaison d'API
Les gabarits de chaînes peuvent être utilisés pour générer des types de points de terminaison d'API basés sur une URL de base et un ensemble de paramètres. Cela peut aider à garantir la cohérence et la sécurité des types lors de l'utilisation d'API.
type BaseURL = "https://api.example.com";
type Resource = "users" | "products";
type ID = string | number;
type GetEndpoint = `${BaseURL}/${T}/${U}`;
// Exemples
type UserEndpoint = GetEndpoint<"users", 123>; // "https://api.example.com/users/123"
type ProductEndpoint = GetEndpoint<"products", "abc-456">; // "https://api.example.com/products/abc-456"
Génération de Code
Dans des scénarios plus avancés, les types de gabarits de chaînes peuvent être utilisés dans le cadre de processus de génération de code. Par exemple, pour générer des requêtes SQL basées sur un schéma ou créer des composants d'interface utilisateur basés sur un fichier de configuration.
Internationalisation (i18n)
Les gabarits de chaînes peuvent être précieux dans les scénarios d'internationalisation (i18n). Par exemple, considérons un système où les clés de traduction suivent une convention de nommage spécifique :
type SupportedLanguages = 'en' | 'es' | 'fr';
type TranslationKeyPrefix = 'common' | 'product' | 'checkout';
type TranslationKey = `${TPrefix}.${string}`;
// Exemple d'utilisation :
const getTranslation = (key: TranslationKey, lang: SupportedLanguages): string => {
// Simule la récupération de la traduction à partir d'un bundle de ressources basé sur la clé et la langue
const translations: Record> = {
'common.greeting': {
en: 'Hello',
es: 'Hola',
fr: 'Bonjour',
},
'product.description': {
en: 'A fantastic product!',
es: '¡Un producto fantástico!',
fr: 'Un produit fantastique !',
},
};
const translation = translations[key]?.[lang];
return translation || `Traduction non trouvée pour la clé : ${key} dans la langue : ${lang}`;
};
const englishGreeting = getTranslation('common.greeting', 'fr'); // Bonjour
const spanishDescription = getTranslation('product.description', 'es'); // ¡Un producto fantástico!
const unknownTranslation = getTranslation('nonexistent.key' as TranslationKey, 'en'); // Traduction non trouvée pour la clé : nonexistent.key dans la langue : en
Le type TranslationKey garantit que toutes les clés de traduction suivent un format cohérent, ce qui simplifie le processus de gestion des traductions et prévient les erreurs.
Limites
Bien que les types de gabarits de chaînes soient puissants, ils ont aussi des limites :
- Complexité : La logique d'analyse complexe peut rapidement devenir difficile à lire et à maintenir.
- Performance : Une utilisation intensive des types de gabarits de chaînes peut avoir un impact sur les performances à la compilation, en particulier dans les grands projets.
- Lacunes de Sécurité des Types : Comme le montre l'exemple de validation d'e-mail, les vérifications à la compilation ne sont parfois pas suffisantes. La validation à l'exécution est toujours nécessaire pour les cas où des données externes doivent respecter des formats stricts.
Meilleures Pratiques
Pour utiliser efficacement les types de gabarits de chaînes, suivez ces meilleures pratiques :
- Restez Simple : Décomposez la logique d'analyse complexe en types plus petits et gérables.
- Documentez Vos Types : Documentez clairement le but et l'utilisation de vos types de gabarits de chaînes.
- Testez Vos Types : Créez des tests unitaires pour vous assurer que vos types se comportent comme prévu.
- Équilibrez la Validation à la Compilation et à l'Exécution : Utilisez les types de gabarits de chaînes pour la validation de base et des vérifications à l'exécution pour les scénarios plus complexes.
Conclusion
Les types de gabarits de chaînes de TypeScript offrent un moyen puissant et flexible de manipuler les types de chaînes à la compilation. En combinant les gabarits de chaînes avec des types conditionnels et l'inférence de type, vous pouvez créer une logique sophistiquée au niveau des types pour analyser, valider et transformer des données basées sur des chaînes. Bien qu'il y ait des limites à prendre en compte, les avantages de l'utilisation des types de gabarits de chaînes en termes de sécurité des types et de maintenabilité du code peuvent être significatifs.
En maîtrisant ces techniques avancées, les développeurs peuvent créer des applications TypeScript plus robustes et fiables.
Exploration Supplémentaire
Pour approfondir votre compréhension des types de gabarits de chaînes, envisagez d'explorer les sujets suivants :
- Types Mappés : Apprenez à transformer des types d'objets en fonction des types de gabarits de chaînes.
- Types Utilitaires : Explorez les types utilitaires intégrés de TypeScript qui peuvent être utilisés en conjonction avec les types de gabarits de chaînes.
- Types Conditionnels Avancés : Plongez plus profondément dans les capacités des types conditionnels pour une logique de type plus complexe.